package rlm;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.LayoutManager2;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import javax.swing.JPanel;
import javax.swing.JScrollPane;

import linearProgramming.LinearProgrammingException;

import org.drools.runtime.ClassObjectFilter;
import org.drools.runtime.StatefulKnowledgeSession;
import org.drools.runtime.rule.FactHandle;

import rlm.constraints.Constraint;
import rlm.rlcontainer.PlaceHolder;
import rlm.util.StringUtils;
import alm.ALMException;

public class RuleLayoutManager implements LayoutManager2, LayoutManager {

	
	private KBaseManager kmanager;
	private KBaseManager kmanagerSystem;
	private State state;
	
	// collections for
	// TODO:: add all the working memory related things to the kbase manager
	// -> maybe build a kbase manager that will handle several kbases (for system and user)
	private List<Constraint> constraintList;
	private List<Component> components;
	private ArrayList userFacts;
	private Map<String, Object> userGlobals;
	
	private JScrollPane scroll;
	private JPanel scrollPanel;
	
	private boolean active;
		
	private Container parent;
	private LayoutManager baseLM;
	private ALMExtended almBase;
	
	
	public RuleLayoutManager(LayoutManager base, String... files) throws ALMException{
		this();
		initRules(files);
		baseLM = base;
		scrollPanel.setLayout(baseLM);
	}
	
	public RuleLayoutManager(String... files) throws ALMException {
		this();
		initRules(files);
	}
	
	public RuleLayoutManager(File ... files) throws ALMException {
		this();
		initRules(files);
	}
	
	public RuleLayoutManager() throws ALMException {
		super();

		baseLM = null;
		almBase = new ALMExtended();
		
		// init collections
//		compToConst = new TreeMap<Component, Constraint>();
		constraintList = new ArrayList<Constraint>();
		components = new ArrayList<Component>();
		userFacts = new ArrayList();
		userGlobals = new TreeMap<String, Object>();
		
		try {
			kmanagerSystem = new KBaseManager();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		state = new State();
		state.setCurrent(State.START);
		
		scrollPanel = new JPanel();
		scroll = new JScrollPane(scrollPanel, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		scrollPanel.setLayout(baseLM);
	}

	private void initRules(String ... files){
		try {
			kmanager = new KBaseManager(files);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	private void initRules(File ... files) {
		try {
			kmanager = new KBaseManager(files);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	@SuppressWarnings("unchecked")
	public void addFact(Object fact){
		userFacts.add(fact);
	}
		
	/**
	 * Components are added to place holders, by their names.
	 * If a component has no name, an automatic name will be generated.
	 * 
	 * The constraints object is added using Constraint<T> 
	 * if the constraint is string, and the component has no name, then 
	 * the constraint string is used as it's name - LATER!!!
	 * ALL COMPONENTS HAVE A NAME
	 * 
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void addLayoutComponent(Component comp, Object constraints) {
		if (comp == null)
			return;
	
		if (comp == scroll)
			return;
		
		if (constraints != null){
			Constraint con = new Constraint(comp, constraints);
			//compToConst.put(comp, con);
			constraintList.add(con);
		}
		
		// add to scroll panel panel
		scrollPanel.add(comp, constraints);
		
//		LATER!!!
//		if (constraints instanceof String && StringUtils.isEmpty(comp.getName()))
//			addLayoutComponent((String)constraints, comp);
//		else
//			addLayoutComponent(comp.getName(), comp);
		// assume it has a name, if not, assign a name
		addLayoutComponent(comp.getName(), comp);
		
	}

	/**
	 * - primary add - 
	 * Adds the component to the place holder with the given name.
	 * if name != comp.getName(), the name param wins
	 */
	@Override
	public void addLayoutComponent(String name, Component comp) {
		if (comp == null || comp == scroll || comp == scrollPanel)
			return;
		
		if (StringUtils.isEmpty(name))
			comp.setName(almBase.createName(comp));
		
		components.add(comp);
	}
	
	public void unloadFile(String name){
		kmanager.unleadFile(name);
	}
	
	public void loadFile(String name){
		kmanager.loadFile(name);
	}
	

	/**
	 * The order of events:
	 * 1. run base layout
	 * 1.2 	calculate resulting places
	 * 2. apply rules
	 * 3. run super layout
	 * 
	 * 
	 * Order of events:
	 *  1. clear all, and init 
	 *  2. run base layout
	 *  3. init and apply rules 
	 *  4. calculate the layout spec (alm) 
	 *  5. set the scroll thingie
	 */
	@Override
	public void layoutContainer(Container parent) {
		if (active)
			return;
		this.parent = parent;
		long start = System.currentTimeMillis();
//		System.out.println("< "+ parent.getWidth() + " :: "+ this.scrollPanel.getWidth() + " :: " + this.scroll.getWidth());
		active = true;
//		setScrollAsContent(parent);
		
		// clean
		
		
		//base
		preLayoutBase(parent);
		
		// init session and apply rules
		applyRules(parent);		

		// run base alm, with or without stealing layout from 
		runALMBase(parent);
		
		setScrollAsContent(parent);
		
		// invalidate everything here?
		active = false;
//		System.out.println("- >");
//		System.out.println(System.currentTimeMillis() - start + ", ");
	}

	/**
	 * run the alm base, after all placeholder rules have been calculated by the rules
	 *
	 * pre condition: at this time, base alm is clear of placeholders, and I add new place holders
	 * @param parent
	 * @param places
	 */
	private void runALMBase(Container parent) {
		
		// someone should calculate missing bounds
//		try {
			almBase.layoutContainer(parent);
//		} catch (LinearProgrammingException e) {
//			System.out.println("rumAlmBase: error calculating layout: " + e.getMessage());
//		}
	}

	/**
	 * Chronological changes:
	 * 1. places were extracted from the working memory
	 * 2. stopped doing this, because simple places are added automatically, and compound are added through rules 
	 * @param parent
	 */
	private void applyRules(Container parent) {
		StatefulKnowledgeSession ksession = setupSession(parent, kmanager);
		insertFacts(ksession);
		insertUserFacts(ksession);
		ksession.fireAllRules();
//		Collection<PlaceHolder> places = extractFacts(ksession);
		
		StatefulKnowledgeSession systemSession = setupSession(parent, kmanagerSystem);
//		systemSession.getAgenda().getAgendaGroup("insertInsets").setFocus();
//		systemSession.getAgenda().getAgendaGroup("arrangeEqually").setFocus();
		systemSession.getAgenda().getAgendaGroup("missingTabs").setFocus();
		insertFacts(systemSession);
		systemSession.fireAllRules();
		
//		systemSession.getAgenda().getAgendaGroup("arrangeEqually").setFocus();
//		systemSession.fireAllRules();
//		Collection<PlaceHolder> nextPlaces = extractFacts(systemSession);
		
//		HashSet<PlaceHolder> set = new HashSet<PlaceHolder>(places);
//		set.addAll(nextPlaces);
		ksession.dispose();
		systemSession.dispose();
	}

	
	/**
	 * @param ksession
	 */
	private void insertUserFacts(StatefulKnowledgeSession ksession) {
		for (Object o : userFacts)
			ksession.insert(o);

		for (Entry<String, Object> entry : userGlobals.entrySet()) {
			ksession.setGlobal(entry.getKey(), entry.getValue());
		}

	}

	private Collection<PlaceHolder> extractFacts(StatefulKnowledgeSession ksession) {
		Collection<?> objects = ksession.getObjects(new ClassObjectFilter(PlaceHolder.class) {
			@Override
			public boolean accept(Object arg0) {
				return false;
			}
		});
		
		ArrayList<PlaceHolder> places = new ArrayList<PlaceHolder>();
		for(Object o : objects){
			if (o instanceof PlaceHolder)
				places.add((PlaceHolder)o);
		}
		
		return places;
	}

	/**
	 * prepares both underlying layouts for the layout process
	 * 
	 * 1. clears the alm base layout
	 * 2. applies the base layout if exists
	 *    build alm model based on the base layout
	 * @param parent
	 */
	private void preLayoutBase(Container parent) {

		//almBase.init(parent.getComponents());
		almBase.init(components);
		
		almBase.setPreferredSize(parent.getSize());		
		
		if (baseLM == null)
			return;
		
		scrollPanel.setLayout(baseLM);
		scrollPanel.doLayout();
		scrollPanel.setLayout(null);

		try {
			almBase.recoverLayout(parent);
		} catch (LinearProgrammingException e) {
			System.out.println("RLM.preLayoutBase: error recovering layout by alm");
		}
	}

	private void setScrollAsContent(Container parent) {
		parent.add(scroll);
		scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
		scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		
		Dimension parentSize = parent.getSize();
		Insets insets = parent.getInsets();
		scroll.setLocation(0,0);
		scroll.setSize(dimMinusInsets(parentSize, insets));
		scroll.setPreferredSize(scroll.getSize());
		scroll.getViewport().setSize(scroll.getSize());
		
		scrollPanel.setSize(dimMinusInsets(scroll.getSize(), scroll.getInsets()));
//		scrollPanel.setBackground(Color.blue);
		scrollPanel.setPreferredSize(scrollPanel.getSize());
//		System.out.println(scroll.getSize() + ", "+scroll.getViewport().getSize()+", " + scrollPanel.getSize());
	}
	
	
	private Dimension dimMinusInsets(Dimension dim, Insets in){
		return new Dimension(dim.width - in.left - in.right, dim.height - in.bottom - in.top);
	}

	/**
	 * Setup the new session for the current layout
	 * @param parent
	 * @return 
	 */
	private StatefulKnowledgeSession setupSession(Container parent, KBaseManager manager) {
		StatefulKnowledgeSession ksession = manager.newKsession();
		// all done, fire all rules
		ksession.setGlobal("height", parent.getHeight());
		ksession.setGlobal("width", parent.getWidth());
		ksession.setGlobal("parent", parent);
		ksession.setGlobal("base", almBase);
		
		//TODO:: needed for missingTabs calculation, should be replaced?
		ksession.setGlobal("ls", almBase.getLayoutSpec());
		
		return ksession;
	}
	
	
	private void insertFacts(StatefulKnowledgeSession ksession){
		insertFacts(ksession, null);
	}
	
	/**
	 * primary
	 * 
	 * Facts that are added to the session:
	 * 1. components
	 * 2. constraints
	 * 3. user facts
	 * 4. state
	 * 5. place holders, generated by alm
	 * @param ksession
	 * @param facts
	 */
	private void insertFacts(StatefulKnowledgeSession ksession, Collection<?> facts){
		for(Component comp : components){
			ksession.insert(comp);
		}
		for (Constraint con : constraintList)
			ksession.insert(con);
			
		// TODO:: insert them in reverse order
		for (PlaceHolder place : almBase.getPlaces())
			ksession.insert(place);
		
		// set state
		this.state.setCurrent(State.START);
		insertOrUpdate(state, ksession);
		
		if (facts != null)
			for (Object o : facts)
				insertOrUpdate(o, ksession);
		
		// insert data:
		ksession.insert(getLayoutDataFactory().getData(parent));
	}
	
	/**
	 * @return
	 */
	private LayoutDataFactory getLayoutDataFactory() {
		return (new DefaultLayoutDataFactory());
	}

	private void insertOrUpdate(Object o, StatefulKnowledgeSession ksession) {
		FactHandle factHandle = ksession.getFactHandle(o);
		if (factHandle == null)
			ksession.insert(o);
		else
			ksession.update(factHandle, o);
	}

	/**
	 * Cleans the remains of previous layout and calculations. Both in the
	 * ksession and in the super layout.
	 * 
	 * The following operations should occur: 1. All previous areas are remoeved
	 * from the ksession 2. All previous areas are removed from the layout spec
	 * 3. layout spec is invalidated 4. components are reinserted into the
	 * ksession 5. State is set to START
	 * 
	 */
	@Override
	public void invalidateLayout(Container target) {
		almBase.destroy();
		// TODO:: destroy without init assumes that layout container will be run after invalidate layout
	}

	
	@Override
	public float getLayoutAlignmentX(Container target) {
		return 0;
	}

	@Override
	public float getLayoutAlignmentY(Container target) {
		return 0;
	}

	@Override
	public Dimension maximumLayoutSize(Container target) {
		return null;
	}

	@Override
	public Dimension minimumLayoutSize(Container parent) {
		return null;
	}

	@Override
	public Dimension preferredLayoutSize(Container parent) {
		return null;
	}

	@Override
	public void removeLayoutComponent(Component comp) {
		
	}
	
	public void autoUpdate(boolean update){
		if (update)
			kmanager.startNotifications();
		else
			kmanager.stopNotifications();
	}
	
	public void addGlobal(String name, Object obj){
		userGlobals.put(name, obj);
	}
}
